using System;
using System.Globalization;
using System.Text.RegularExpressions;

namespace Neo.Core.Qualifiers
{
    //--------------------------------------------------------------------------------------
    //	Base class for string predicates (abstract)
    //--------------------------------------------------------------------------------------

    /// <summary>
    /// Abstract base class for predicates that work with string values.
    /// </summary>
    public abstract class StringPredicateBase
    {
        protected string predValue;

        /// <summary>
        /// Initializes a new instance of the <see cref="StringPredicateBase"/> class.
        /// </summary>
        /// <param name="aValue">A value.</param>
        public StringPredicateBase(string aValue)
        {
            predValue = aValue;
        }

        /// <summary>
        /// Gets the value.
        /// </summary>
        /// <value>The value.</value>
        public object Value
        {
            get { return predValue; }
        }

        /// <summary>
        /// Sets the predicate value.
        /// </summary>
        /// <param name="aValue">A value.</param>
        public void SetPredicateValue(object aValue)
        {
            predValue = (string) aValue;
        }
    }

    ///<summary>
    ///</summary>
    public abstract class StringArrayPredicateBase
    {
        protected string[] predValue;

        /// <summary>
        /// Initializes a new instance of the <see cref="StringArrayPredicateBase"/> class.
        /// </summary>
        /// <param name="aValue">A value.</param>
        public StringArrayPredicateBase(string[] aValue)
        {
            predValue = aValue;
        }

        /// <summary>
        /// Gets the value.
        /// </summary>
        /// <value>The value.</value>
        public object Value
        {
            get { return predValue; }
        }

        /// <summary>
        /// Sets the predicate value.
        /// </summary>
        /// <param name="aValue">A value.</param>
        public void SetPredicateValue(object aValue)
        {
            predValue = (string[]) aValue;
        }

        protected string MakeArgList(string[] predValue, string action)
        {
            string outVal = "";
            for (int i = 0; i < predValue.Length; i++)
            {
                if (i < predValue.Length - 1)
                {
                    outVal += string.Format("\"{0}\" {1} ", predValue[i].Trim(), action);
                }
                else
                {
                    outVal += string.Format("\"{0}\"", predValue[i].Trim());
                }
            }
            return outVal;
        }
    }

    //--------------------------------------------------------------------------------------
    //	Like
    //--------------------------------------------------------------------------------------

    /// <summary>
    /// Predicate that returns true if the string matches the pattern.
    /// </summary>
    public class LikePredicate : StringPredicateBase, IMutablePredicate
    {
        protected string pattern;

        /// <summary>
        /// Initializes a new instance of the <see cref="LikePredicate"/> class.
        /// </summary>
        /// <param name="aValue">A value.</param>
        public LikePredicate(string aValue) : base(aValue)
        {
            if (aValue == null)
                throw new ArgumentException("The like predicate requires a non-null value.");

            aValue = Regex.Escape(aValue);
            pattern = "^" + aValue.Replace("%", ".*") + "$";
        }

        /// <summary>
        /// Determines whether [is true for value] [the specified a value].
        /// </summary>
        /// <param name="aValue">A value.</param>
        /// <param name="nullVal">The null val.</param>
        /// <returns>
        /// 	<c>true</c> if [is true for value] [the specified a value]; otherwise, <c>false</c>.
        /// </returns>
        public bool IsTrueForValue(object aValue, object nullVal)
        {
            string aValueAsString;

            if (aValue == nullVal)
                return false;

            if ((aValueAsString = aValue as String) == null)
                throw new ArgumentException("The like predicate can only be used with strings.");

            return Regex.IsMatch(aValueAsString, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline);
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </returns>
        public override string ToString()
        {
            return ("like " + predValue);
        }
    }


    /// <summary>
    /// Predicate that returns true if the string is equal to the string stored in the
    /// predicate not taking case into account.
    /// </summary>
    /// <remarks>
    /// This predicate does not necessarily translate into SQL.
    /// </remarks>
    public class CaseInsensitiveEqualsPredicate : StringPredicateBase, IMutablePredicate
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="CaseInsensitiveEqualsPredicate"/> class.
        /// </summary>
        /// <param name="aValue">A value.</param>
        public CaseInsensitiveEqualsPredicate(string aValue) : base(aValue)
        {
        }

        /// <summary>
        /// Determines whether [is true for value] [the specified a value].
        /// </summary>
        /// <param name="aValue">A value.</param>
        /// <param name="nullVal">The null val.</param>
        /// <returns>
        /// 	<c>true</c> if [is true for value] [the specified a value]; otherwise, <c>false</c>.
        /// </returns>
        public bool IsTrueForValue(object aValue, object nullVal)
        {
            if (aValue == nullVal)
                return false;

            CompareInfo compInfo = CultureInfo.CurrentCulture.CompareInfo;

            return (compInfo.Compare((string) aValue, predValue, CompareOptions.IgnoreCase) == 0);
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </returns>
        public override string ToString()
        {
            return (predValue != null) ? ("equals (case insensitive) " + predValue) : "null";
        }
    }

    //--------------------------------------------------------------------------------------
    //	StartsWith
    //--------------------------------------------------------------------------------------

    /// <summary>
    /// Predicate that returns true if the string begins with the string stored in the
    /// predicate.
    /// </summary>
    public class StartsWithPredicate : StringPredicateBase, IMutablePredicate
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="StartsWithPredicate"/> class.
        /// </summary>
        /// <param name="aValue">A value.</param>
        public StartsWithPredicate(string aValue) : base(aValue)
        {
        }

        /// <summary>
        /// Determines whether [is true for value] [the specified a value].
        /// </summary>
        /// <param name="aValue">A value.</param>
        /// <param name="nullVal">The null val.</param>
        /// <returns>
        /// 	<c>true</c> if [is true for value] [the specified a value]; otherwise, <c>false</c>.
        /// </returns>
        public bool IsTrueForValue(object aValue, object nullVal)
        {
            if (aValue == nullVal)
                return false; // null starts with nothing, not even null...?

            CompareInfo compInfo = CultureInfo.CurrentCulture.CompareInfo;
            return compInfo.IsPrefix((string) aValue, predValue, CompareOptions.IgnoreCase);
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </returns>
        public override string ToString()
        {
            return (predValue != null) ? ("startswith " + predValue) : "null";
        }
    }


    //--------------------------------------------------------------------------------------
    //	EndsWith
    //--------------------------------------------------------------------------------------

    /// <summary>
    /// Predicate that returns true if the string ends with the string stored in the
    /// predicate.
    /// </summary>
    public class EndsWithPredicate : StringPredicateBase, IMutablePredicate
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="EndsWithPredicate"/> class.
        /// </summary>
        /// <param name="aValue">A value.</param>
        public EndsWithPredicate(string aValue) : base(aValue)
        {
        }

        /// <summary>
        /// Determines whether [is true for value] [the specified a value].
        /// </summary>
        /// <param name="aValue">A value.</param>
        /// <param name="nullVal">The null val.</param>
        /// <returns>
        /// 	<c>true</c> if [is true for value] [the specified a value]; otherwise, <c>false</c>.
        /// </returns>
        public bool IsTrueForValue(object aValue, object nullVal)
        {
            if (aValue == nullVal)
                return false; // null ends with nothing, not even null...?

            CompareInfo compInfo = CultureInfo.CurrentCulture.CompareInfo;
            return compInfo.IsSuffix((string) aValue, predValue, CompareOptions.IgnoreCase);
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </returns>
        public override string ToString()
        {
            return (predValue != null) ? ("endswith " + predValue) : "null";
        }
    }


    //--------------------------------------------------------------------------------------
    //	Contains
    //--------------------------------------------------------------------------------------

    /// <summary>
    /// Predicate that returns true if the string contains the string stored in the
    /// predicate.
    /// </summary>
    public class ContainsPredicate : StringPredicateBase, IMutablePredicate
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ContainsPredicate"/> class.
        /// </summary>
        /// <param name="aValue">A value.</param>
        public ContainsPredicate(string aValue) : base(aValue)
        {
        }

        /// <summary>
        /// Determines whether [is true for value] [the specified a value].
        /// </summary>
        /// <param name="aValue">A value.</param>
        /// <param name="nullVal">The null val.</param>
        /// <returns>
        /// 	<c>true</c> if [is true for value] [the specified a value]; otherwise, <c>false</c>.
        /// </returns>
        public bool IsTrueForValue(object aValue, object nullVal)
        {
            if (aValue == nullVal)
                return false; // null contains nothing, not even null...?

            string newValue = predValue.Replace("*", String.Empty);

            CompareInfo compInfo = CultureInfo.CurrentCulture.CompareInfo;
            return (compInfo.IndexOf((string) aValue, newValue, CompareOptions.IgnoreCase) != -1);
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </returns>
        public override string ToString()
        {
            if (predValue == null)
            {
                throw new NullReferenceException("ContainsPredicate requires a predicate value.");
            }
            return " (*, '\"" + predValue + "\"')";
        }
    }

    ///<summary>
    ///</summary>
    public class ContainsAndPredicate : StringArrayPredicateBase, IMutablePredicate
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ContainsAndPredicate"/> class.
        /// </summary>
        /// <param name="aValue">A value.</param>
        public ContainsAndPredicate(params string[] aValue) : base(aValue)
        {
        }

        /// <summary>
        /// Determines whether [is true for value] [the specified a value].
        /// </summary>
        /// <param name="aValue">A value.</param>
        /// <param name="nullVal">The null val.</param>
        /// <returns>
        /// 	<c>true</c> if [is true for value] [the specified a value]; otherwise, <c>false</c>.
        /// </returns>
        public bool IsTrueForValue(object aValue, object nullVal)
        {
            if (aValue == nullVal)
                return false; // null contains nothing, not even null...?

            if (aValue is String == false)
            {
                return false;
            }

            int matches = 0;
            CompareInfo compInfo = CultureInfo.CurrentCulture.CompareInfo;

            foreach (string pred in predValue)
            {
                string predTxt = pred.Replace("*", String.Empty);
                if (compInfo.IndexOf((string) aValue, predTxt, CompareOptions.IgnoreCase) != -1)
                {
                    matches++;
                }
            }
            return matches == predValue.Length;
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </returns>
        public override string ToString()
        {
            if (predValue == null)
            {
                throw new NullReferenceException("ContainsAndPredicate requires a predicate value.");
            }

            return " (*, '" + MakeArgList(predValue, "and") + "')";
        }
    }

    ///<summary>
    ///</summary>
    public class ContainsOrPredicate : StringArrayPredicateBase, IMutablePredicate
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ContainsOrPredicate"/> class.
        /// </summary>
        /// <param name="aValue">A value.</param>
        public ContainsOrPredicate(params string[] aValue) : base(aValue)
        {
        }

        /// <summary>
        /// Determines whether [is true for value] [the specified a value].
        /// </summary>
        /// <param name="aValue">A value.</param>
        /// <param name="nullVal">The null val.</param>
        /// <returns>
        /// 	<c>true</c> if [is true for value] [the specified a value]; otherwise, <c>false</c>.
        /// </returns>
        public bool IsTrueForValue(object aValue, object nullVal)
        {
            if (aValue == nullVal)
                return false; // null contains nothing, not even null...?

            if (aValue is String == false)
            {
                return false;
            }

            CompareInfo compInfo = CultureInfo.CurrentCulture.CompareInfo;

            foreach (string pred in predValue)
            {
                string predTxt = pred.Replace("*", String.Empty);
                if (compInfo.IndexOf((string) aValue, predTxt, CompareOptions.IgnoreCase) != -1)
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
        /// </returns>
        public override string ToString()
        {
            if (predValue == null)
            {
                throw new NullReferenceException("ContainsOrPredicate requires a predicate value.");
            }

            return " (*, '" + MakeArgList(predValue, "or") + "')";
        }
    }
}